#include "tms_ui.hpp"

TmsWin::TmsWin(Application *app)
    : app(app),
      is_input_echo(tms::DINPUT_ECHO),
      info_str(tms::DINFO),
      promote_str(tms::DPROMOTE),
      menu_location_str(tms::DMENU_LOC),
      time_str(tms::DTIME_STR),
      app_version_str(tms::DAPPVERSION)
{
#ifdef TMS_WIN32_PLATFORM
    // 解决 windows 编码问题
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);

    // 启用VT100转义序列支持（Windows 10+）
#ifdef ENABLE_VIRTUAL_TERMINAL_PROCESSING
    DWORD mode = 0;
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleMode(hOut, &mode);
    mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    SetConsoleMode(hOut, mode);
#endif

#endif

    // 初始化终端大小
#ifdef TMS_WIN32_PLATFORM
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
    {
        this->win_h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
        this->win_w = csbi.srWindow.Right - csbi.srWindow.Left + 1;
    }
    else
    {
        this->win_h = 80;
        this->win_w = 80;
    }

#endif

#ifdef TMS_POSIX_PLATFORM
    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
    this->win_h = static_cast<int>(w.ws_row);
    this->win_w = static_cast<int>(w.ws_col);
#endif
};

TmsWin::~TmsWin() {};

inline unsigned int TmsWin::get_line_cnt()
{
    unsigned int cnt = 0;
    unsigned int length = this->info_str.length();

    for (unsigned int i = 0; i < length; i++)
    {
        if ('\n' == this->info_str[i])
        {
            cnt++;
        }
    }

    return cnt;
}

void TmsWin::update_top_bar()
{
    this->menu_location_str = app->get_menu_location();
    this->time_str = app->get_current_time();
    this->app_version_str = app->get_app_version();
}

void TmsWin::refresh()
{
    // 绘制界面，准备输入，初步处理输入
    // draw 先计算信息区，提示区，输入区高度，其中只有信息区需要动态计算
    // 计算后获得换行高度，直接输出到屏幕上，随后等待用户输入
    // draw 同时负责获取输入，并将输入写入交互数据对象

    // 清屏
#ifdef _WIN32
    std::system("cls");
#else
    std::system("clear");
#endif

    // top menu
    std::stringstream top_bar_menu_left, top_bar_menu_right;
    top_bar_menu_left << menu_location_str << tms::DTIME_PREFIX << time_str;
    top_bar_menu_right << tms::DVERSION_PRIFIX << app_version_str;
    int blank_rows_top = this->win_w - top_bar_menu_left.str().size() - top_bar_menu_right.str().size();
    std::cout << top_bar_menu_left.str();
    for (int i = 0; i < blank_rows_top; i++)
    {
        std::cout << " ";
    }
    std::cout << top_bar_menu_right.str() << std::endl;

    // info
    int blank_rows = this->win_h - tms::UI_RESERVED_HEIGHT - this->get_line_cnt();
    std::cout << info_str;
    for (int i = 0; i < blank_rows; i++)
    {
        std::cout << std::endl;
    }

    // promote
    std::cout << promote_str;
    std::cout << std::endl;

    // 关闭控制台回显
    if (!this->is_input_echo)
    {
#ifdef _WIN32
        HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
        DWORD mode;
        GetConsoleMode(hStdin, &mode);
        mode &= ~ENABLE_ECHO_INPUT;
        SetConsoleMode(hStdin, mode);
#else
        struct termios t;
        tcgetattr(STDIN_FILENO, &t);
        t.c_lflag &= ~ECHO;
        tcsetattr(STDIN_FILENO, TCSANOW, &t);
#endif
    }

    // 处理输入
    std::getline(std::cin, this->input_l_cache);

    // 恢复控制台回显
    if (!this->is_input_echo)
    {
#ifdef _WIN32
        HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
        DWORD mode;
        GetConsoleMode(hStdin, &mode);
        mode |= ENABLE_ECHO_INPUT;
        SetConsoleMode(hStdin, mode);
#else
        struct termios t;
        tcgetattr(STDIN_FILENO, &t);
        t.c_lflag |= ECHO;
        tcsetattr(STDIN_FILENO, TCSANOW, &t);
#endif
    }
}

TmsWin &operator<<(TmsWin &w, const tms::Info &dw)
{
    w.info_str = dw.info;

    return w;
}

TmsWin &operator>>(TmsWin &w, tms::StringI &dw)
{
    w.update_top_bar();
    w.promote_str = dw.promote;
    w.refresh();

    dw.i = w.input_l_cache;

    return w;
}

TmsWin &operator>>(TmsWin &w, const tms::StringI &dw)
{
    w.update_top_bar();
    w.promote_str = dw.promote;
    w.refresh();

    return w;
}

TmsWin &operator>>(TmsWin &w, tms::PasswdI &dw)
{
    w.is_input_echo = 0;
    w.update_top_bar();
    w.promote_str = dw.promote;
    w.refresh();

    dw.i = w.input_l_cache;
    w.is_input_echo = 1;

    return w;
}

TmsWin &operator>>(TmsWin &w, tms::IntI &dw)
{
    w.update_top_bar();
    w.promote_str = dw.promote;
    w.refresh();

    dw.i = std::stoi(w.input_l_cache);

    return w;
}

TmsWin &operator>>(TmsWin &w, tms::DoubleI &dw)
{
    w.update_top_bar();
    w.promote_str = dw.promote;
    w.refresh();

    dw.i = std::stod(w.input_l_cache);

    return w;
}

TmsWin &operator>>(TmsWin &w, tms::OptionI &dw)
{
    w.update_top_bar();
    w.promote_str = dw.promote;
    w.refresh();

    // 首先筛选字符数，然后筛选字符
    if (w.input_l_cache.size() != 1)
    {
        throw std::invalid_argument(tms::INPUT_INVALID);
    }

    if (w.input_l_cache == std::string("Y") || w.input_l_cache == std::string("y"))
    {
        dw.i = OPT_YES;
    }
    else if (w.input_l_cache == std::string("N") || w.input_l_cache == std::string("n"))
    {
        dw.i = OPT_NO;
    }

    return w;
}

BaseMenuNode::BaseMenuNode(Application *app, const std::string &n_name, BaseMenuNode *par_node) : app(app), n_name(n_name), par_node(par_node) {};

BaseMenuNode::~BaseMenuNode()
{
    for (size_t i = 0; i < this->child_node.size(); i++)
    {
        delete this->child_node[i];
    }
};

int BaseMenuNode::execute()
{
    // 默认不执行任何操作
    return 0;
};

void BaseMenuNode::add_child(BaseMenuNode *child_node)
{
    this->child_node.push_back(child_node);
};

std::string BaseMenuNode::get_n_name() const
{
    return this->n_name;
};

std::string BaseMenuNode::path2string(std::string spacer) const
{
    std::vector<std::string> cache = this->get_locator();
    std::stringstream output_cache;

    for (size_t i = 0; i < cache.size(); i++)
    {
        output_cache << cache[i] << (i == (cache.size() - 1) ? "" : spacer);
    }

    return output_cache.str();
};

std::vector<std::string> BaseMenuNode::get_locator() const
{
    std::vector<std::string> cache;

    if (this->par_node)
    {
        cache = this->par_node->get_locator();
        cache.push_back(this->get_n_name());
        return cache;
    }
    else
    {
        cache.push_back(this->get_n_name());
        return cache;
    }
};

SelectionNode::SelectionNode(Application *app, const std::string &n_name, BaseMenuNode *par_node)
    : BaseMenuNode(app, n_name, par_node),
      app(app) {};

void SelectionNode::befor_loop() {};

void SelectionNode::after_loop() {};

void SelectionNode::init_loop() {};

void SelectionNode::exit_loop() {};

int SelectionNode::execute()
{
    try
    {
        this->init_loop();

        for (int user_choice = 0;; user_choice = 0)
        {
            if (this->child_node.empty())
            {
                throw SelectionExitError("Empty child.");
            }

            this->befor_loop();

            std::stringstream info_ss, prom_ss;

            // 创建提示字符串
            for (size_t i = 0; i < this->child_node.size(); i++)
            {
                info_ss << tms::TAB << i << ": ";
                info_ss << this->child_node[i]->get_n_name();
                info_ss << std::endl;
            }
            info_ss << tms::TAB << this->child_node.size() << ": ";
            info_ss << tms::EXIT_NODE << std::endl;
            prom_ss << tms::MENU_SELECT_L << this->child_node.size() << tms::MENU_SELECT_R;
            prom_ss << this->child_node.size() << tms::MENU_SELECT_END;

            app->mw << tms::Info(info_ss.str());
            tms::IntI choice(prom_ss.str());

            try
            {
                app->mw >> choice;
            }
            catch (const std::invalid_argument &e)
            {
                continue;
            }

            // 处理选项，正常选项立刻执行选择，退出立刻退出
            if (choice.i >= 0 && choice.i < static_cast<int>(this->child_node.size()))
            {
                app->set_active_node(this->child_node[choice.i]);
                this->child_node[choice.i]->execute();
                app->set_active_node(this);
            }
            else if (choice.i == static_cast<int>(this->child_node.size()))
            {
                throw SelectionExitError("Normal exit.");
            }
            else
            {
                continue;
            }

            this->after_loop();
        }
    }
    catch (const SelectionExitError &e)
    {
        // 正常退出
    }

    this->exit_loop();

    return 0;
};

Application::Application()
    : menu(new SelectionNode(this, tms::HOME_TITLE, nullptr)),
      act_node(menu),
      l_status(new TmsLoginStatus()),
      mw(TmsWin(this)) {};

Application::~Application()
{
    delete this->menu;
    delete this->l_status;
}

std::string Application::get_current_time()
{
    std::time_t now = std::time(nullptr);
    std::tm *local_time = std::localtime(&now);
    if (!local_time)
    {
        return "";
    }

    std::ostringstream oss;
    oss << std::put_time(local_time, "%H:%M");
    return oss.str();
}

std::string Application::get_app_version()
{
    return static_cast<std::string>(tms::TMS_VERSION);
}

std::string Application::get_menu_location()
{
    return act_node->path2string();
}

void Application::set_active_node(BaseMenuNode *n)
{
    this->act_node = n;
}

int Application::mainloop()
{
    return 0;
}

#ifdef _TMS_UI_TEST
/*
UI 测试逻辑
这将按顺序测试字符串输入
测试函数不一定按照进度维护，异常的测试应该被修复，但取决于开发者的时间
*/
// TODO 测试构建崩溃了，需要修复

int main(void)
{
    Application app;
    tms::StringI t_str("Input string");
    tms::PasswdI t_pwd("Input passwd");
    tms::IntI t_int("Input int");
    tms::DoubleI t_dou("Input double");
    tms::OptionI t_opt("Input yes");

    app.mw << tms::Info("Hello new UI!");
    app.mw >> t_str;
    app.mw << tms::Info("Last: " + t_str.i);
    app.mw >> t_pwd;
    app.mw << tms::Info("Last: " + t_pwd.i);
    app.mw >> t_int;
    app.mw << tms::Info("Last: " + std::to_string(t_int.i + 10));
    app.mw >> t_dou;
    app.mw << tms::Info("Last: " + std::to_string(t_dou.i + 0.5));
    app.mw >> t_opt;
    app.mw << tms::Info("Last: " + (OPT_YES == t_opt.i) ? "YES" : "NO");
    app.mw >> t_str;
}

#endif